package com.bjy.qa.agent.interceptor.tester;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bjy.qa.agent.exception.MyException;
import com.bjy.qa.agent.model.Step;
import com.bjy.qa.agent.tools.juel.JuelHelper;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 计算签名 Interceptor
 */
public class SignInterceptor extends BeforeInterceptor {
    // bjy openapi 签名
    private static final String SIGN_BJY_PATTERN_STR = "\"\\$\\{auth:sign_bjy\\((.*?)\\)\\}\""; // 百家云自动签名 正则表达式
    private static final Pattern SIGN_BJY_PATTERN = Pattern.compile(SIGN_BJY_PATTERN_STR); // 截取 百家云自动签名 正则表达式（通过这个可以获得百家云自动签名使用的 secret_key）
    private static final String SIGN_BJY_PREFIX = "${auth:sign_bjy("; // 百家云签名 前缀

    // SaaS 服务 OpenAPI 调用 PaaS 服务的 API 接口签名
    private static final String SIGN_SAAS_TO_API_PATTERN_STR = "\"\\$\\{auth:sign_saastoapi\\((.*?)\\)\\}\""; // SaaS 调用 PaaS API 自动签名 正则表达式
    private static final Pattern SIGN_SAAS_TO_API_PATTERN = Pattern.compile(SIGN_SAAS_TO_API_PATTERN_STR); // 截取 SaaS 调用 PaaS API 自动签名 正则表达式（通过这个可以获得 SaaS 调用 PaaS API 自动签名使用的 secret_key）
    private static final String SIGN_SAAS_TO_API_PREFIX = "${auth:sign_saastoapi("; // SaaS 调用 PaaS API 签名 前缀

    // PaaS 服务 BMCU、BLive 调用 PaaS 服务的 API 接口签名
    private static final String SIGN_SERVICE_TO_API_PATTERN_STR = "\"\\$\\{auth:sign_servicetoapi\\((.*?)\\)\\}\""; // BMCU、BLive 调用 PaaS API 自动签名 正则表达式
    private static final Pattern SIGN_SERVICE_TO_API_PATTERN = Pattern.compile(SIGN_SERVICE_TO_API_PATTERN_STR); // 截取 BMCU、BLive 调用 PaaS API自动签名 正则表达式（通过这个可以获得 BMCU、BLive 调用 PaaS API自动签名使用的 secret_key）
    private static final String SIGN_SERVICE_TO_API_PREFIX = "${auth:sign_servicetoapi("; // BMCU、BLive 调用 PaaS API 签名 前缀

    private static final String SIGN_BJY_HOLD = "__bjySign__"; // 签名 占位符

    private String singType = ""; // 签名类型，根据这个类型来决定使用哪种签名方式

    /**
     * 计算签名 前置 拦截器 构造方法
     * @param globalParas 全局参数
     * @param step step 数据
     */
    public SignInterceptor(Map<String, Object> globalParas, Step step) {
        super(globalParas, step);
    }

    @Override
    protected String executeUrl(Map<String, Object> globalParas, String url) {
        return url;
    }

    @Override
    protected String executeExtra1(String url, Map<String, Object> globalParas, String extra) {
        if (extra.indexOf(SIGN_BJY_PREFIX) >= 0) {
            this.singType = "bjy";
            return autoSignBjy(globalParas, extra); // 百家云自动签名
        } else if (extra.indexOf(SIGN_SAAS_TO_API_PREFIX) >= 0) {
            this.singType = "saastoapi";
            return autoSignBjy(globalParas, extra); // SAAS 调用 PaaS API 自动签名
        } else if (extra.indexOf(SIGN_SERVICE_TO_API_PREFIX) >= 0) {
            this.singType = "servicetoapi";
            return autoSignBjy(globalParas, extra); // BMCU、BLive 调用 PaaS API 自动签名
        } else {
            return extra;
        }
    }

    @Override
    protected String executeExtra2(String url, Map<String, Object> globalParas, String extra) {
        if (extra.indexOf(SIGN_BJY_PREFIX) >= 0) {
            this.singType = "bjy";
            return autoSignBjy(globalParas, extra); // 百家云自动签名
        } else if (extra.indexOf(SIGN_SAAS_TO_API_PREFIX) >= 0) {
            this.singType = "saastoapi";
            return autoSignBjy(globalParas, extra); // SAAS 调用 PaaS API 自动签名
        } else if (extra.indexOf(SIGN_SERVICE_TO_API_PREFIX) >= 0) {
            this.singType = "servicetoapi";
            return autoSignBjy(globalParas, extra); // BMCU、BLive 调用 PaaS API 自动签名
        } else {
            return extra;
        }
    }

    @Override
    protected String executeExtra3(String url, Map<String, Object> globalParas, String extra) {
        if (extra.indexOf(SIGN_BJY_PREFIX) >= 0) {
            this.singType = "bjy";
            return autoSignBjy(globalParas, extra); // 百家云自动签名
        } else if (extra.indexOf(SIGN_SAAS_TO_API_PREFIX) >= 0) {
            this.singType = "saastoapi";
            return autoSignBjy(globalParas, extra); // SAAS 调用 PaaS API 自动签名
        } else if (extra.indexOf(SIGN_SERVICE_TO_API_PREFIX) >= 0) {
            this.singType = "servicetoapi";
            return autoSignBjy(globalParas, extra); // BMCU、BLive 调用 PaaS API 自动签名
        } else {
            return extra;
        }
    }

    @Override
    protected String executeExtra4(String url, Map<String, Object> globalParas, String extra) {
        return extra;
    }

    @Override
    protected String executeExtra5(String url, Map<String, Object> globalParas, String extra) {
        return extra;
    }

    /**
     * 百家云签名
     * @param globalParas 全局参数
     * @param paramStr 参数字符串（遍历所有参数，并自动计算签名）
     * @return
     */
    private String autoSignBjy(Map<String, Object> globalParas, String paramStr) {
        String signKey = null; // 签名字段，会将签名后的字符放到到此字段中
        String secretKey = null; // 签名密钥

        // 获取 secret_key
        Matcher matcher;
        if (singType.equals("bjy")) {
            matcher = SIGN_BJY_PATTERN.matcher(paramStr);
        } else if (singType.equals("saastoapi")) {
            matcher = SIGN_SAAS_TO_API_PATTERN.matcher(paramStr);
        } else if (singType.equals("servicetoapi")) {
            matcher = SIGN_SERVICE_TO_API_PATTERN.matcher(paramStr);
        } else {
            throw new RuntimeException("未知签名类型！");
        }
        if (matcher.find()) { // 自动签名字段，保存 签名密钥
            secretKey = matcher.group(1); // 获取 secret_key

            // 如果获取的 secret_key 是变量，则替换为实际值
            for (Map.Entry<String, Object> entry : globalParas.entrySet()) {
                if (entry.getKey().equals(secretKey)) {
                    secretKey = entry.getValue().toString();
                    break;
                }
            }

            if (singType.equals("bjy")) {
                paramStr = paramStr.replaceAll(SIGN_BJY_PATTERN_STR, "\"" + SIGN_BJY_HOLD + "\""); // 用签名字段替换为占位符，例如，会将 "sign": "${auth:sign_bjy(partner_key)}" 替换为 "sign": "__bjySign__"
            } else if (singType.equals("saastoapi")) {
                paramStr = paramStr.replaceAll(SIGN_SAAS_TO_API_PATTERN_STR, "\"" + SIGN_BJY_HOLD + "\""); // 用签名字段替换为占位符，例如，会将 "sign": "${auth:sign_saastoapi(partner_key)}" 替换为 "sign": "__bjySign__"

            } else if (singType.equals("servicetoapi")) {
                paramStr = paramStr.replaceAll(SIGN_SERVICE_TO_API_PATTERN_STR, "\"" + SIGN_BJY_HOLD + "\""); // 用签名字段替换为占位符，例如，会将 "sign": "${auth:sign_servicetoapi(partner_key)}" 替换为 "sign": "__bjySign__"
            }
        }

        // 签名前，先把带参数的字段替换为实际值
        try {
            if (StringUtils.isNotBlank(paramStr)) {
                paramStr = JuelHelper.getValue(globalParas, paramStr);
            }
        } catch (Exception e) {
            throw new MyException("Juel 表达式异常：" + e.getMessage());
        }

        JSONObject paramJsonObject = JSON.parseObject(paramStr); // 将参数字符串转为 JSONObject

        // 遍历带自动签名参数，组装成数组
        List<String> paramArrayList = new ArrayList<>(); // 保存拼接后的待计算签名的字符串，保存后如上例子
        for (Map.Entry<String, Object> entry : paramJsonObject.entrySet()) {
            if (entry.getValue().toString().equals(SIGN_BJY_HOLD)) { // 是签名字段，保存签名字段名
                signKey = entry.getKey();
            } else { // 不是签名字段，直接拼接
                paramArrayList.add(entry.getKey());
                paramArrayList.add(entry.getValue().toString());
            }
        }
        // 向 paramArrayList 中添加最后的 secret_key 字段
        if (signKey == null || secretKey == null) {
            throw new MyException("签名中未匹配到关键字，必须以 ${auth:sign_bjy( 开头和 )} 结尾，且前后不能有空格");
        } else {
            paramArrayList.add("secret_key");
            paramArrayList.add(secretKey);
        }

        String singKey = signBJY(paramArrayList); // 计算签名

        paramJsonObject.put(signKey, singKey); // 将签名字段替换成计算好的签名

        return paramJsonObject.toJSONString();
    }

    /**
     * BJY 签名
     * @param paramList
     * @return
     */
    private String signBJY(List<String> paramList) {
        if ((paramList.size() % 2) != 0) {
            throw new RuntimeException("bjy 验签算法错误：参数个数必须为偶数");
        }

        // 将 String[] 转为参数 map
        Map<String, String> map = new HashMap<>();
        int argIndex = 0;
        while (argIndex < paramList.size()) {
            map.put(paramList.get(argIndex), paramList.get(argIndex + 1));
            argIndex = argIndex + 2;
        }

        // 根据key排序并声称待签名名文（例如：key1=value1&key2=value2&...&keyN=valueN&partner_key=key）
        StringBuilder sb = new StringBuilder();
        AtomicReference<String> signKey = new AtomicReference<>("");
        map.entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> {
            if ("secret_key".equalsIgnoreCase(entry.getKey())) {
                if (singType.equals("bjy")) {
                    signKey.set("partner_key=" + entry.getValue());
                } else if (singType.equals("saastoapi")) {
                    signKey.set("key=" + entry.getValue());
                } else if (singType.equals("servicetoapi")) {
                    signKey.set("" + entry.getValue());
                }
            } else {
                sb.append(entry.getKey() + "=" + entry.getValue() + "&");
            }
        });
        sb.append(signKey.get());

        if (singType.equals("bjy")) {
            return DigestUtils.md5Hex(sb.toString()); // 名文计算 md5 并返回
        } else if (singType.equals("saastoapi")) {
            return DigestUtils.md5Hex(sb.toString()).toUpperCase(); // 名文计算 md5 并返回
        } else if (singType.equals("servicetoapi")) {
            return DigestUtils.md5Hex(sb.toString()); // 名文计算 md5 并返回
        } else {
            throw new RuntimeException("未知签名类型！");
        }
    }
}
